﻿using Jint;
using ScriptEngine.Script.Extension;
using System;
using System.Reflection;
using System.Threading;
using FxSuperCore.Log;
using ScriptEngine.Script.Exceptions;
using Jint.Runtime;
using System.IO;
using Jint.Native;
using Jint.Native.Object;

namespace ScriptEngine.Script.Executor.Impl
{
	public class JavaScriptExecutor : AbstractScriptExecutor
	{
		/// <summary>
		/// js引擎
		/// </summary>
		private volatile Engine jsEngine;

		public ILogPrint Logger { get; set; } = new PluginLoggerPrinter("JavaScriptExecutor", new ConsoleLogPrinter());


		/// <summary>
		/// 从数据源中获取C#方法、属性注册到Js环境
		/// </summary>
		/// <param name="engine">js引擎</param>
		private void RegisterFunction(Engine engine)
		{
			foreach (CodeTipPojo codeTipPojo in Datasource)
			{
				if (codeTipPojo.ScriptCallCsharp.Type == ExtensionType.Func)
				{
					// 注入方法
					ScriptCallCsharp scriptCallCsharp = codeTipPojo.ScriptCallCsharp;
					MethodInfo methodInfo = codeTipPojo.Instance as MethodInfo;
					Delegate methodDelegate = Delegate.CreateDelegate(scriptCallCsharp.DelegateType, codeTipPojo.ParentObj,
						methodInfo.Name);
					engine.SetValue(codeTipPojo.RealName, methodDelegate);
				}
				else
				{
					// 注入常量或属性
					engine.SetValue(codeTipPojo.RealName, codeTipPojo.Instance);
				}
			}
		}

		public override void ExecFunc(string funcName, params object[] args)
		{
			AssertEngineIsRunning();
			Monitor.Enter(locker);
			try
			{
				JsValue[] funcArgs = new JsValue[args.Length];
				for (var i = 0; i < args.Length; i++)
				{
					funcArgs[i] = JsValue.FromObject(jsEngine, args[i]);
				}

				jsEngine.GetValue(funcName).Call(funcArgs);
			}
			catch (ArgumentException e)
			{
				Logger.Error($"Not find symbol:{funcName},detail:{e.Message}");
				throw e;
			}
			catch (JavaScriptException e)
			{
				Logger.Error(
					$"js running execption  symbol:{funcName} detail:{e.Message}{(e.JavaScriptStackTrace == null ? "" : "\n" + e.JavaScriptStackTrace)}");
				throw e;
			}
			catch (Exception e)
			{
				Logger.Error($"js running func unknow error symbol:{funcName} detail:{e.Message}");
			}
			finally
			{
				Monitor.Exit(locker);
			}
		}

		public override T ExeFunc<T>(string funcName, params object[] args)
		{
			AssertEngineIsRunning();
			Monitor.Enter(locker);
			try
			{
				JsValue[] funcArgs = new JsValue[args.Length];
				for (var i = 0; i < args.Length; i++)
				{
					funcArgs[i] = JsValue.FromObject(jsEngine, args[i]);
				}

				var res = jsEngine.GetValue(funcName).Call(funcArgs);
				if (typeof(T) == typeof(string))
				{
					return (T)((object)res.AsString());
				}

				if (typeof(T) == typeof(int))
				{
					int result = (int)res.AsNumber();
					return (T)((object)result);
				}

				if (typeof(T) == typeof(float))
				{
					float result = (float)res.AsNumber();
					return (T)((object)result);
				}

				if (typeof(T) == typeof(double))
				{
					return (T)((object)res.AsNumber());
				}

				if (typeof(T) == typeof(bool))
				{
					return (T)((object)res.AsBoolean());
				}

				if (typeof(T) == typeof(string))
				{
					return (T)((object)res.AsString());
				}

				if (typeof(T).IsAssignableFrom(typeof(ObjectInstance)) || typeof(T) == typeof(object))
				{
					return (T)(object)res.AsObject();
					;
				}
			}
			catch (ArgumentException e)
			{
				Logger.Error($"Not find symbol:{funcName},detail:{e.Message}");
				throw e;
			}
			catch (JavaScriptException e)
			{
				Logger.Error(
					$"js running execption  symbol:{funcName} detail:{e.Message}{(e.JavaScriptStackTrace == null ? "" : "\n" + e.JavaScriptStackTrace)}");
				throw e;
			}
			catch (Exception e)
			{
				Logger.Error($"js running func unknow error symbol:{funcName} detail:{e.Message}");
			}
			finally
			{
				Monitor.Exit(locker);
			}

			return default;
		}

		public override void RunWithCode(string code)
		{
			if (IsRunning)
			{
				throw new ScriptException("Script already running");
			}
			Monitor.Enter(locker);
			try
			{
				jsEngine = new Engine(cfg => { cfg.AllowClr(); });
				EngineStatus = ScriptEngineStatus.PREPARE_RUNNING;
				RegisterFunction(jsEngine);
				jsEngine.Execute(code);
				EngineStatus = ScriptEngineStatus.RUNNING;
			}
			catch (ThreadAbortException)
			{
				Logger.Info("force exit with ThreadAbortException");
				Exit();
				return;
			}
			catch (JavaScriptException e)
			{
				Logger.Error($"js script parse exception: {e.Message}{Environment.NewLine}{e.JavaScriptStackTrace}");
				Exit();
				return;
			}
			catch (Exception e)
			{
				Logger.Error($"js script load exception: {e.Message}");
				Exit();
				return;
			}
			finally
			{
				Monitor.Exit(locker);
			}
			HandleEventLoop();
		}

		public override void RunWithPath(string filepath)
		{
			if (File.Exists(filepath) == false)
			{
				throw new ScriptException($"[{filepath}] not exist");
			}
			string jsCode = File.ReadAllText(filepath);
			RunWithCode(jsCode);
		}

		public override object GetObject(string symbol)
		{
			AssertEngineIsRunning();
			return jsEngine.GetValue(symbol).AsObject();
		}

		public override void Exit()
		{
			base.Exit();
			if (IsRunning)
			{
				jsEngine?.Advanced.ResetCallStack();
				jsEngine?.Dispose();
				jsEngine = null;
				EngineStatus = ScriptEngineStatus.EXITED;
				Logger.Info("js script exec done");
			}
		}
	}
}
